/*
 *  Copyright 2007 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.logic;

import javax.servlet.jsp.tagext.SimpleTagSupport;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.JspTagException;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.net.MalformedURLException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.taglib.TagUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.blandware.atleap.webapp.util.core.WebappUtil;
import com.blandware.atleap.webapp.util.core.WebappConstants;
import com.blandware.atleap.webapp.util.core.ApplicationResources;
import com.blandware.atleap.webapp.struts.ContentTilesRequestProcessor;
import com.blandware.atleap.model.core.ContentField;

/**
 * <p>Base for tags that iterate over some collection of objects: for instance,
 * pages or resources.</p>
 * <p><a href="BaseObjectIteratorTag.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.5 $ $Date: 2007/05/05 10:42:13 $
 */
public abstract class BaseObjectIteratorTag extends SimpleTagSupport {

    protected transient final Log log = LogFactory.getLog(BaseObjectIteratorTag.class);
    protected static final String NUMBER_KEY = "com.blandware.atleap.taglib.logic.BASE_OBJECT_ITERATOR_TAG_NUMBER";

    /**
     * Prefix of URIs by which to fetch
     */
    protected String uriPrefix = null;

    /**
     * ORDER BY clause
     */
    protected String orderBy = null;

    /**
     * Limit
     */
    protected Integer limit = null;

    /**
     * Offset
     */
    protected Integer offset = null;

    /**
	 * Name of variable to export result of each iteration
	 */
	protected String var;

	/**
	 * Scope to export result of each iteration to. Default value is "page"
	 */
	protected String scope = "page";

    /**
     * Context-relative URI of Struts Action to call to create a first
     * collection element
     */
    protected String createAction = null;

    /**
     * Name of global ActionForward to use to create a first collection
     * element
     */
    protected String createForward = null;

    /**
     * Context-relative URI of action to call to update create a first
     * collection element
     */
    protected String createUri = null;

    /**
     * Influences what will be done if no objects were found for
     * iterating: if so and true, tag body will be rendered once, else in
     * edit mode wrapper DIV for creating first collection element will be
     * created
     */
	protected Boolean ignore = Boolean.FALSE;

    /**
     * Returns URI prefix.
     *
     * @return URI prefix
     * @see #uriPrefix
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="URI prefix to use when fetching data"
     */
    public String getUriPrefix() {
        return uriPrefix;
    }

    /**
     * Sets URI prefix.
     *
     * @param uriPrefix prefix to set
     */
    public void setUriPrefix(String uriPrefix) {
        this.uriPrefix = uriPrefix;
    }

    /**
     * Returns ORDER BY clause.
     *
     * @return ORDER BY clause
     * @see #orderBy
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="ORDER BY clause to use when fetching data"
     */
    public String getOrderBy() {
        return orderBy;
    }

    /**
     * Sets ORDER BY clause.
     *
     * @param orderBy clause to set
     */
    public void setOrderBy(String orderBy) {
        this.orderBy = orderBy;
    }

    /**
     * Returns limit.
     *
     * @return limit
     * @see #limit
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Limit to use when fetching data"
     */
    public Integer getLimit() {
        return limit;
    }

    /**
     * Sets limit.
     *
     * @param limit limit to set
     */
    public void setLimit(Integer limit) {
        this.limit = limit;
    }

    /**
     * Returns offset.
     *
     * @return offset
     * @see #offset
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Offset to use when fetching data"
     */
    public Integer getOffset() {
        return offset;
    }

    /**
     * Sets offset.
     *
     * @param offset offset to set 
     */
    public void setOffset(Integer offset) {
        this.offset = offset;
    }

    /**
     * Returns name of variable to save result of each iteration under.
     *
     * @return variable name
     * @see #var
     * @jsp.attribute required="true"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of variable to save result of each iteration under"
     */
    public String getVar() {
        return var;
    }

    /**
     * Set name of variable to save result of each iteration under.
     *
     * @param var variable name to set
     */
    public void setVar(String var) {
        this.var = var;
    }

    /**
     * Returns scope to which result will be saved.
     *
     * @return variable scope
     * @see #scope
     * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope of variable to save result"
     */
    public String getScope() {
        return scope;
    }

    /**
     * Sets scope to which result will be saved.
     *
     * @param scope scope to set
     */
    public void setScope(String scope) {
        this.scope = scope;
    }

    /**
     * Returns Struts Action for creating first element.
     *
     * @return Struts Action for creating first element
     * @see #createAction
     * @jsp.attribute required="false"
     * rtexprvalue="true"
     * type="java.lang.String"
     * description="Context-relative URI of Struts Action to call to create a first collection element"
     */
    public String getCreateAction() {
        return createAction;
    }

    /**
     * Sets Struts Action for creating first element.
     *
     * @param action Struts Action to set
     * @see #createAction
     */
    public void setCreateAction(String action) {
        this.createAction = action;
    }

    /**
     * Returns ActionForward for creating first element.
     *
     * @return ActionForward for creating first element
     * @see #createForward
     * @jsp.attribute required="false"
     * rtexprvalue="true"
     * type="java.lang.String"
     * description="Name of global ActionForward to use to create a first collection element"
     */
    public String getCreateForward() {
        return createForward;
    }

    /**
     * Sets ActionForward for creating first element.
     *
     * @param forward ActionForward to set
     * @see #createForward
     */
    public void setCreateForward(String forward) {
        this.createForward = forward;
    }

    /**
     * Returns URI for creating first element.
     *
     * @return URI for creating first element
     * @see #createUri
     * @jsp.attribute required="false"
     * rtexprvalue="true"
     * type="java.lang.String"
     * description="Context-relative URI to call to create a first collection element"
     */
    public String getCreateUri() {
        return createUri;
    }

    /**
     * Sets URI for creating first element.
     *
     * @param uri URI to set
     * @see #createUri
     */
    public void setCreateUri(String uri) {
        this.createUri = uri;
    }

    /**
     * Returns whether to ignore errors.
     *
     * @return whether to ignore errors
     * @see #ignore
     * @jsp.attribute required="false"
     * rtexprvalue="true"
     * type="java.lang.Boolean"
     * description="If it is true and there are no objects the tag body will be shown one time, else in edit mode wrapper DIV for creating of first collection element will be rendered"
     */
    public Boolean getIgnore() {
        return ignore;
    }

    /**
     * Sets whether to ignore errors.
     *
     * @param ignore whether to ignore errors
     */
    public void setIgnore(Boolean ignore) {
        this.ignore = ignore;
    }

    /**
     * Fetches collection to iterate over.
     *
     * @return collection
     */
    abstract protected Collection fetchCollection();

    /**
     * Returns resource key for message output in edit mode when nothing to
     * iterate.
     *
     * @return resource key
     */
    abstract protected String getEmptyCollectionMessageKey();

    /**
     * Returns Spring bean by its name.
     *
     * @param name bean name
     * @return bean
     */
    protected Object getBean(String name) {
        PageContext pageContext = (PageContext) getJspContext();
        ServletContext servletContext = pageContext.getServletContext();
        WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
        return ctx.getBean(name);
    }

    /**
	 * Fetches objects and iterates over them.
	 *
	 * @throws javax.servlet.jsp.JspException
	 * @throws java.io.IOException
	 */
    public void doTag() throws JspException, IOException {
		// obtain JSP body
		JspFragment body = getJspBody();
		if ( body == null ) {
			return;
		}

        PageContext pageContext = (PageContext) getJspContext();
        HttpServletRequest request
                = (HttpServletRequest) pageContext.getRequest();
        ServletContext servletContext = pageContext.getServletContext();
        TagUtils tagUtils = TagUtils.getInstance();
        int varScope = PageContext.PAGE_SCOPE;
        if ( scope != null ) {
            varScope = tagUtils.getScope(scope);
        }

        // calculate tag number
		Integer tagNumber = (Integer) pageContext.getAttribute(NUMBER_KEY,
                PageContext.REQUEST_SCOPE);
		if ( tagNumber == null ) {
			tagNumber = new Integer(0);
		} else {
			tagNumber = new Integer(tagNumber.intValue() + 1);
		}
        pageContext.setAttribute(NUMBER_KEY, tagNumber,
                PageContext.REQUEST_SCOPE);

        Collection collection = fetchCollection();
        Iterator i = null;
        if (collection != null) {
            i = collection.iterator();
        }
        if (i != null && i.hasNext()) {
            while (i.hasNext()) {
                Object obj = i.next();

                // expose current object to the specified scope
                if ( var != null ) {
                    pageContext.setAttribute(var, obj, varScope);
                }

                // invoke body content
                body.invoke(null);
            }
        } else {
            // no collection or it's empty
            Object editModeEnabledAttr = request.getSession().getAttribute(WebappConstants.SITE_EDIT_MODE_ENABLED_KEY);
            boolean editModeEnabled = editModeEnabledAttr != null && Boolean.TRUE.equals(editModeEnabledAttr);
            if ( ignore.booleanValue() ) {
                if ( body != null ) {
                    body.invoke(null);
                }
            } else if (editModeEnabled) {
                createUri = calculateUri(createAction, createForward, createUri,
                        request);
                boolean createExists = createUri != null;

                if (createExists) {
                    StringBuffer divId = new StringBuffer("__object__iterator__")
                            .append(tagNumber).append("__wrapper__");
                    String requestUrl = (String) request.getAttribute(ContentTilesRequestProcessor.PROCESSED_URL);
                    if ( requestUrl == null ) {
                        requestUrl = WebappUtil.findGlobalForwardConfig("index",
                                null, request).getPath();
                    }

                    StringBuffer onmouseover = new StringBuffer("doSelectLayer(this.id);");
                    StringBuffer onmouseout = new StringBuffer("doUnselectLayer(this.id);");
                    StringBuffer ondblclick = new StringBuffer("doCallContentUpdate('")
                            .append(createUri).append("', '")
                            .append(requestUrl).append("');");

                    ApplicationResources applicationResources
                            = ApplicationResources.getInstance(servletContext);

                    String divDisplay = "block";
                    String message = applicationResources.getMessage(request,
                            getEmptyCollectionMessageKey());

                    String content = new StringBuffer("<div id=\"")
                            .append(divId).append("\" name=\"").append(divId)
                            .append("\" class=\"fieldValueWrapper\" style=\"display: ")
                            .append(divDisplay).append(";\" ")
                            .append("onmouseover=\"").append(onmouseover)
                            .append("\" onmouseout=\"").append(onmouseout)
                            .append("\" ondblclick=\"").append(ondblclick)
                            .append("\">").append(message).append("</div>")
                            .toString();

                    tagUtils.write(pageContext, content);
                }
            }
        }
    }

    /**
     * Calculates URI based from action, forward, URI.
     *
     * @param action action to use
     * @param forward forward to use
     * @param uri URI to use
     * @param request request currently processed
     * @return calculated URI
     * @throws javax.servlet.jsp.JspTagException thrown if something goes wrong
     */
    protected String calculateUri(String action, String forward,
                                String uri, HttpServletRequest request)
            throws JspTagException {
        String result = uri;
        if ( action != null ) {
			result = WebappUtil.getActionMappingURL(action, null, request, WebappConstants.URL_TYPE_DOMAIN_RELATIVE);
		} else if ( forward != null ) {
			try {
				result = WebappUtil.getActionForwardURL(forward, null, request, WebappConstants.URL_TYPE_DOMAIN_RELATIVE);
			} catch ( MalformedURLException e ) {
				JspTagException ex = new JspTagException(e);
				throw ex;
			}
		}
        return result;
    }
}
